#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>

#include "fixture.h"
#include "lista.h"
#include "hash.h"

#include "partido.h"
#include "equipo.h"
#include "jugador.h"

#include "parser.h"
#include "codigos_print.h"
#include "print.h"

#define MAXJUGADORES 23
#define MAXCHAR 256

struct fixture {
				hash_t *equipos;//Se guardan equipo_t.
				hash_t *jugadores;//Se guardan jugador_t.
				partido_t** campeonato;//Se guardan partido_t.
				size_t cant_partidos;
				char* goleador;//Se guarda el nombre del goleador
				};

/**********************************************************************
*						FUNCIONES FIXTURE
***********************************************************************/
fixture_t *fixture_crear(void)
{
	fixture_t *fixture = malloc(sizeof(fixture_t));

	if (!fixture)
		return NULL;
	fixture->equipos = hash_crear(equipo_destruir);
	if (!fixture->equipos)
		{
			free(fixture);
			return NULL;
		}
	
	fixture->jugadores = hash_crear(jugador_destruir);
	if (!fixture->jugadores)
		{
			hash_destruir(fixture->equipos);
			free(fixture);
			return NULL;
		}

	//modificado 10/11
	fixture->campeonato = NULL;
	fixture->cant_partidos = 0;
	fixture->goleador = NULL;
	return fixture;
}

void fixture_destruir(fixture_t *fixture)
{
	if (!fixture)
		return;
	
	hash_destruir(fixture->equipos);
	hash_destruir(fixture->jugadores);
	for (int i = 0; i < fixture->cant_partidos; ++i)
		partido_destruir (fixture->campeonato[i]);
	
	free(fixture->campeonato);
	free (fixture->goleador);
	free(fixture);
}


error_code fixture_agregar_equipo(fixture_t *fixture, const char *nombre)
{
	if (!fixture || !nombre)
		return ERROR_PARAMETRO;

	equipo_t *equipo = equipo_crear(nombre);
	
	if (!equipo)
		return ERROR_MEMORIA;

	if (!hash_guardar(fixture->equipos,nombre,equipo))
		{
			equipo_destruir(equipo);
			return ERROR_MEMORIA;
		}

	return EXITO;
}

error_code fixture_agregar_jugador(fixture_t *fixture, const char *equipo, const char *nombre)
{
	if (!fixture || !equipo || !nombre)
		return ERROR_PARAMETRO;

	equipo_t *miequipo = hash_obtener(fixture->equipos,equipo);
	
	if (!miequipo)
		return EQUIPO_NO_HAY;

	size_t dorsal = equipo_agregar_jugador(miequipo,nombre);
	
	if (!dorsal)
		return ERROR_MEMORIA;

	jugador_t *jugador = jugador_crear(nombre,equipo,dorsal);
	
	if (!jugador)
		{
			equipo_quitar_ultimo_jugador(miequipo);
			return ERROR_MEMORIA;
		}

	if (!hash_guardar(fixture->jugadores,nombre,jugador))
		{
			jugador_destruir(jugador);
			equipo_quitar_ultimo_jugador(miequipo);
			return ERROR_MEMORIA;
		}

	return EXITO;
}

//modificado 10//11 autor: Joaquin Blanco, 94653;
/*
*Crea la estructura que representa al fixture del campeonato 
*implementado sobre un arreglo en donde se almacenan cada uno de 
*los partidos del evento.
*Pre: La lista de equipos debe poseer un largo (cantidad de
*equipos) igual a N, donde N = 2^k donde k es un numero entero.
*Post: Devuelve EXITO si el proceso se llevo a cabo.*/
error_code organizar_mundial (fixture_t* fixture, lista_t* equipos)
{
	if (!fixture || !equipos)
		return ERROR_PARAMETRO;
	int fecha = (lista_largo(equipos) / 2) - 1;
  	char* local = NULL;
	char* visitante = NULL;
	fixture->campeonato = calloc(fixture->cant_partidos, sizeof (partido_t*));
	if (!fixture->campeonato)
		return ERROR_MEMORIA;
	/*Se crean las estructuras necesarias para la primera fase
	del campeonato.*/
	while (!lista_esta_vacia (equipos))
	{
		local = lista_borrar_primero (equipos);
		visitante = lista_borrar_primero (equipos);
		fixture->campeonato[fecha] = partido_crear(local, visitante);
		free (local);
		free (visitante);
		++fecha;
	}

	return EXITO;
}

// 14/11 15:24 -- Maxi
error_code fixture_cargar(fixture_t* fixture, FILE *file)
{
	if (!fixture || !file)
		return ERROR_PARAMETRO;

	char *equipo = NULL;
	char *linea = NULL;
	int cant_jugadores = 0;

	lista_t *equipos = lista_crear();
	if (!equipos)
		return ERROR_MEMORIA;

	error_code error = EXITO;

	while ((error == EXITO) && ((linea = linea_siguiente(file)) != NULL) && !feof(file))
	{
		if (!cant_jugadores)
    	{
			equipo = linea;//guardo nombre del equipo para usar despues.
			error = fixture_agregar_equipo (fixture, equipo);
			lista_insertar_ultimo(equipos, equipo);
		}
    	else
		{
			error = fixture_agregar_jugador(fixture, equipo, linea);
			free(linea);// no es necesario guardar nombres de jugadores.
		}

		++cant_jugadores;
		if (cant_jugadores > MAXJUGADORES)
			cant_jugadores = 0;
   		linea = NULL;
	}

	if (linea)
		free(linea);

    // Lo que sea que se tenga en la lista, se borra.
	// Se reinicia el fixture.
	if (error != EXITO)
	{
		fixture_destruir(fixture);
		fixture = fixture_crear ();
		lista_destruir(equipos, NULL);
		PrintErr (error, NULL, NULL); 
		return error;
	}
  
	fixture->cant_partidos = lista_largo (equipos) - 1;
	error = organizar_mundial (fixture, equipos);
	lista_destruir (equipos, NULL);
	return error;
}

jugador_t* fixture_obtener_jugador (fixture_t* fixture, const char* jugador)
{
	if (!fixture)
		return NULL;

	if (!jugador)
		return NULL;

	return hash_obtener (fixture->jugadores, jugador);
}

equipo_t* fixture_obtener_equipo (fixture_t* fixture,const char* equipo)
{
	if (!fixture)
		return NULL;

	if (!equipo)
		return NULL;

	return hash_obtener (fixture->equipos, equipo);
}

/*Funcion de hash inyectiva y por ende perfecta.
*Dado que las claves (idr) estan preestablecidas y son limitadas 
*lo que permite que esta funcion diseñada especialmente para que 
*coincida con una determinada posicion en el arrglo "campeonato"
*de la estructura "fixture".*/
int obtener_partido (char* idr)
{
	int pos = 0;
	int i = 0;

	while (isdigit(idr[i]))
	{      
		pos *= 10;
		pos += idr[i] - '0';
		++i;    
	}

	pos--;
	pos += idr[i] - 'a';
	return pos;
}

partido_t* fixture_obtener_partido (fixture_t* fixture, char* idr_partido)
{
	/*Obtenemos la posicion de l partido al cual se quiere
	acceder a partir de la idr_partido.
	Si la posicion no pertenece a al rango de partidos posibles
	se devuelve NULL.*/
	int pos_partido = obtener_partido (idr_partido);
	if (pos_partido >= fixture->cant_partidos)
		return NULL;

	if (fixture->campeonato[pos_partido])
		return fixture->campeonato[pos_partido];

	/*Verifica que los partidos predecesores hayan sido jugados.
	Obtenemos los ganadores de cada uno. Creamos una nueva 
	estructura partido_t y los asignamos a la posicion que ya 
	obtuvimos.*/
	int pizq = (pos_partido * 2) + 1; 
	int pder = pizq + 1;
	if (!partido_jugado (fixture->campeonato[pizq]))
		return NULL;

	if (!partido_jugado (fixture->campeonato[pder]))
		return NULL;

	const char* local = partido_obtener_ganador(fixture->campeonato[pizq]);
	const char* visitante = partido_obtener_ganador(fixture->campeonato[pder]);
	fixture->campeonato[pos_partido] = partido_crear(local, visitante);
	return fixture->campeonato [pos_partido];
}

void fixture_actualizar_goleador (fixture_t* fixture, jugador_t* jugador)
{
	if (!fixture->goleador)
	{
		const char* nombre = jugador_obtener_nombre(jugador);
		fixture->goleador = malloc(sizeof(char) * (strlen(nombre) + 1));
		strcpy(fixture->goleador, nombre);
		return;
	}
	
	jugador_t* goleador = fixture_obtener_jugador(fixture, fixture->goleador);
	if (!goleador)
		return;
	
	if (jugador_obtener_goles(jugador) > jugador_obtener_goles(goleador))
	{
		const char* nuevo_goleador = jugador_obtener_nombre (jugador);
		char* aux = malloc(sizeof(char) * (strlen(nuevo_goleador) + 1));
		if (!aux)
			return;

		free (fixture->goleador);
		fixture->goleador = aux;
		strcpy (fixture->goleador, nuevo_goleador);
	}
}
		
jugador_t* fixture_obtener_goleador (fixture_t* fixture)
{
	if (!fixture)
		return NULL;

	if (!fixture->goleador)
		return NULL;

	return hash_obtener (fixture->jugadores, fixture->goleador);
}
		
/**********************************************************************
*			COMANDOS PRINCIPALES
***********************************************************************/
	
void agregar_resultado (fixture_t* fixture, lista_t* parametros)
{
	/*
		Actualizamos los datos del partido en el fixture.
	*/
	char* idr_partido = lista_borrar_primero (parametros);
	if (!idr_partido)
	{
		PrintErr (ERROR_PARAMETRO, NULL, NULL);
		return;
	}
	
	partido_t* partido = fixture_obtener_partido (fixture, idr_partido);
	if (!partido)
	{
		PrintErr (RESULTADO_NO_HAY, idr_partido, NULL);
		free(idr_partido);
		return;
	}
	
	if (partido_jugado(partido))
	{
		PrintErr (RESULTADO_REPETIDO, idr_partido, NULL);
		free(idr_partido);
		return;
	}
	
	free(idr_partido);
	char* gol1 = lista_borrar_primero (parametros);
	char* gol2 = lista_borrar_primero (parametros);

	int gloc = atoi(gol1);
	int gvis = atoi(gol2);

	free (gol1);
	free (gol2);
	
	partido_actualizar (partido, gloc, gvis);

	/*
		Actualizamos los datos de los jugadores dentro del fixture.
	*/
	const char* local = partido_obtener_local (partido);
	const char* visitante = partido_obtener_visitante (partido);
	const char* player = NULL;
	equipo_t* equipo = NULL;
	jugador_t* jugador = NULL;

	equipo = fixture_obtener_equipo (fixture, local);
	for (; gloc > 0; --gloc)
	{
		char* aux = lista_borrar_primero (parametros);
		int dorsal = atoi (aux);
		player = equipo_obtener_jugador (equipo, dorsal);
		jugador = fixture_obtener_jugador (fixture, player);
		jugador_agregar_gol (jugador);
		fixture_actualizar_goleador (fixture, jugador);
		free (aux);
	}

	equipo = fixture_obtener_equipo (fixture, visitante);
	for (; gvis > 0; --gvis)
	{
		char* aux = lista_borrar_primero (parametros);
		int dorsal = atoi (aux);
		player = equipo_obtener_jugador (equipo, dorsal);
		jugador = fixture_obtener_jugador (fixture, player);
		jugador_agregar_gol (jugador);
		fixture_actualizar_goleador (fixture, jugador);
		free (aux);
	}

	PrintErr (EXITO, NULL, NULL);
}

/*
*Procesa los datos de los jugadores para luego imprimirlos en 
*pantalla.
Post: devuelve true si el proceso fue llevado a cabo.*/
bool visit_jugadores (const char* clave, void* dato, void* extra)
{
	char* player = dato;
	fixture_t* fixture = extra;
	jugador_t* jugador = fixture_obtener_jugador (fixture, player);
	if (!jugador)
		return false;

	const char* nombre = jugador_obtener_nombre (jugador);
	int dorsal = jugador_obtener_dorsal (jugador);
	int goles = jugador_obtener_goles (jugador);
	PrintOk(OK_JUGADORES, (char*)nombre, &dorsal, &goles, NULL);
	return true;
}


void listar_jugadores (fixture_t* fixture, lista_t* parametros)
{
	char* modo = lista_borrar_primero (parametros);
	if (!modo)
	{
		PrintErr (ERROR_PARAMETRO, NULL, NULL);
		return;
	}

	char* nombre_equipo = lista_borrar_primero (parametros);
	if (!nombre_equipo)
	{
		free(modo);
		PrintErr (ERROR_PARAMETRO, NULL, NULL);
		return;
	}

	equipo_t* equipo = fixture_obtener_equipo (fixture, nombre_equipo);
	if (!equipo)
	{
		free(modo);
		PrintErr (EQUIPO_NO_HAY, nombre_equipo, NULL);
		free(nombre_equipo);
		return;
	}
	
	if (!strcmp ("dorsal", modo))
		equipo_listar_plantel(equipo, visit_jugadores, fixture);
	else
		equipo_iterar_plantel(equipo, visit_jugadores, fixture);

	free(modo);
	free(nombre_equipo);
}

void listar_goleador(fixture_t* fixture, lista_t* parametros)
{
	jugador_t* jugador = fixture_obtener_goleador (fixture);
	if (!jugador)
	{
		PrintErr (ERROR_MEMORIA, NULL, NULL);
		return;
	}

	const char* nombre = jugador_obtener_nombre (jugador);
	const char* equipo = jugador_obtener_equipo (jugador);
	int goles = jugador_obtener_goles (jugador);
	PrintOk(OK_GOLEADOR, (char*)nombre, (char*)equipo, &goles, NULL);
}

void goles_jugador (fixture_t* fixture, lista_t* parametros)
{
	char* nombre = lista_ver_primero (parametros);
	jugador_t* jugador = fixture_obtener_jugador (fixture, nombre);
	if (!jugador)
	{
		PrintErr (JUGADOR_NO_HAY, nombre, NULL);
		return;
	}
	
	int dorsal = jugador_obtener_dorsal (jugador);
	const char* equipo = jugador_obtener_equipo (jugador);
	int goles = jugador_obtener_goles (jugador);
	PrintOk(OK_JUGADOR, nombre, &dorsal, (char*)equipo, &goles);
}

void mostrar_resultado (fixture_t* fixture, lista_t* parametros)
{
	char* idr_partido = lista_ver_primero (parametros);
	partido_t* partido = fixture_obtener_partido (fixture, idr_partido);
	if (!partido)
	{
		PrintErr (RESULTADO_NO_HAY, idr_partido, NULL);
		return;
	}

	if (!partido_jugado (partido))
	{
		PrintErr (RESULTADO_NO_HAY, idr_partido, NULL);
		return;
	}
	
	const char* local = partido_obtener_local (partido);
	const char* visitante = partido_obtener_visitante (partido);

	int gloc = partido_obtener_gloc (partido);
	int gvis = partido_obtener_gvis (partido);

	PrintOk(OK_PARTIDO, (char*)local, &gloc, (char*)visitante, &gvis);
}
